﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Windows.Data;
using Microsoft.CSharp;
using Microsoft.CSharp.RuntimeBinder;
using QuickRunnable.Quick;

namespace QuickRunnable {
    public class TestClass {
        private int m_A;

        public TestClass( int i ) {
            m_A = i;

            GetAIndirect = GetA;

            //System.Collections.Generic.Dictionary`2[System.String,MyType]
            PropertyInfo prop = typeof( TestClass ).GetProperty( "A" );
            IndirectAGetter = ( Func< TestClass, int > )
                              Delegate.CreateDelegate( typeof( Func< TestClass, int > ), null, prop.GetGetMethod() );
            UntypedIndirectGetter = Program.Wrap( prop.GetGetMethod() );

        }

        //private Func< object, object[], object > Wrap( MethodInfo method );
        public int A { get { return m_A; } }
        public int GetA() { return m_A; }
        public readonly Func< int > GetAIndirect;

        public static Func< TestClass, int > IndirectAGetter;
        public static Func< object, object[], object > UntypedIndirectGetter;
    }

    public class MyClass {
        public String A { get; set; }
        public String B { get; set; }
    }

    public class BindingTestClass : INotifyPropertyChanged {
        private String m_A;
        public String A { get { return m_A; } set { m_A = value;
            PropertyChanged( this, new PropertyChangedEventArgs( "A" ));
        } }
        public event PropertyChangedEventHandler PropertyChanged;
    }

    internal class Program {
        private static void Main2( string[] args ) {

        }

        public static int current;
        public static Object obj;

        public static void Test( int num_tests ) {
            TestClass[] tests_class = new TestClass[num_tests];

            for( int i = 0 ; i < tests_class.Length ; i++ ) {
                tests_class[ i ] = new TestClass( i );
            }
            Stopwatch stop = Stopwatch.StartNew();
            for( int i = 0 ; i < num_tests ; i++ ) {
                current = tests_class[ i ].GetA();
            }
            stop.Stop();
            Console.WriteLine("Direct method call:" + stop.ElapsedMilliseconds);
            stop = Stopwatch.StartNew();
            for( int i = 0 ; i < num_tests ; i++ ) {
                current = tests_class[ i ].GetAIndirect();
            }
            stop.Stop();
            Console.WriteLine("Func call:" + stop.ElapsedMilliseconds);

            stop = Stopwatch.StartNew();
            for( int i = 0 ; i < num_tests ; i++ ) {
                current =  (int) TestClass.IndirectAGetter( tests_class[ i ] );
            }
            stop.Stop();
            Console.WriteLine("Via open delegate:" + stop.ElapsedMilliseconds);
            Object[] args = new Object[1];
            stop = Stopwatch.StartNew();
            for( int i = 0 ; i < num_tests ; i++ ) {
                current = ( int ) TestClass.UntypedIndirectGetter( tests_class[ i ], null );
            }
            stop.Stop();
            Console.WriteLine( "Via DynamicMethod delegate:" + stop.ElapsedMilliseconds );
            Console.WriteLine();
            Console.WriteLine( current );
            Console.WriteLine();
        }

        public static Func< object, object[], object > Wrap( MethodInfo method ) {
            var dm = new DynamicMethod( method.Name, typeof( object ), new Type[] {
                                                                                      typeof( object ),
                                                                                      typeof( object[] )
                                                                                  }, method.DeclaringType, true );
            var il = dm.GetILGenerator();

            if( !method.IsStatic ) {
                il.Emit( OpCodes.Ldarg_0 );
                il.Emit( OpCodes.Unbox_Any, method.DeclaringType );
            }
            var parameters = method.GetParameters();
            for( int i = 0 ; i < parameters.Length ; i++ ) {
                il.Emit( OpCodes.Ldarg_1 );
                il.Emit( OpCodes.Ldc_I4, i );
                il.Emit( OpCodes.Ldelem_Ref );
                il.Emit( OpCodes.Unbox_Any, parameters[ i ].ParameterType );
            }
            il.EmitCall( method.IsStatic || method.DeclaringType.IsValueType
                             ? OpCodes.Call
                             : OpCodes.Callvirt, method, null );
            if( method.ReturnType == null || method.ReturnType == typeof( void ) ) {
                il.Emit( OpCodes.Ldnull );
            } else if( method.ReturnType.IsValueType ) {
                il.Emit( OpCodes.Box, method.ReturnType );
            }
            il.Emit( OpCodes.Ret );
            return ( Func< object, object[], object > ) dm.CreateDelegate( typeof( Func< object, object[], object > ) );
        }
    }
}